package sim1011.status;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import sim1011.components.Component;
import sim1011.poltergeist.ResourceManager;
import sim1011.starter.EctoMobileStarter;
import sim1011.starter.Logger;

public class Status {
	// livello batteria del nodo mobile
	private float batteryLevel;
	// file delle statistiche
	private FileWriter batteryWriter, globalCostWriter, throughputWriter,
			mobileCpuLoadWriter, mobileMemoryLoadWriter, trafficWriter;
	// hashMap componentName, requisiti di CPU per componente (serve
	// all'arbiter)
	private HashMap<String, Integer> componentsRequiredCPU;
	// hashMap componentName, requisiti di memoria per componente (serve
	// all'arbiter)
	private HashMap<String, Integer> componentsRequiredMemory;
	// costo corrente globale
	private float currentCost;
	// throughuput corrente globale
	private float currentThroughput;
	// costo corrente globale al tick precedente
	private float prevCost;	
	// contatore delle migrazioni	
	private long migrationCounter;
	// matrice delle QoS del collegamento tra i componenti
	private NetworkMatrix networkMatrix;
	// contatore messaggi in entrata dal poltergeist
	private long poltergeistIn;
	// contatore messaggi in uscita dal poltergeist
	private long poltergeistOut;
	//traffico totale
	private long currentTraffic;
	//traffico totale raggiunto al tick i
	private long onTickTraffic;
	//traffico totale raggiunto al tick i-1
	private long prevOnTickTraffic;
	// hashMap resourceMagagerName, costo cpu (impostata all' inizio)
	private HashMap<String, Integer> resourceManagersCPUCost;
	// hashMap resourceMagagerName, sommatoria valori di CPU libera ottenuti
	private HashMap<String, Integer> resourceManagersFreeCPU;
	// hashMap resourceMagagerName, sommatoria valori di memoria libera ottenuti
	private HashMap<String, Integer> resourceManagersFreeMemory;
	// hashMap resourceMagagerName, costo memoria (impostata all' inizio)
	private HashMap<String, Integer> resourceManagersMemoryCost;
	// hashMap resourceMagagerName, cpu Nominale (impostata all' inizio)
	private HashMap<String, Integer> resourceManagersNominalCPU;
	// hashMap resourceMagagerName, memoria Nominale (impostata all' inizio)
	private HashMap<String, Integer> resourceManagersNominalMemory;
	// numero totale dei resourceManager(impostato all'inizio)
	private int resourceManagersNumber;
	// contatore dei messaggi di stato
	private int statusRequestCounter;
	// HashMap che indica dove si trovano i componenti
	private HashMap<String, String> whereAreComponents;

	public Status() {
		statusRequestCounter = 1;
		poltergeistIn = 0;
		poltergeistOut = 0;
		migrationCounter = 0;
		batteryLevel = 0.0f;
		currentCost = 0.0f;
		currentThroughput = 0;
		currentCost = 0.0f;
		currentThroughput = 0;
		prevCost = 0.0f;
		currentTraffic = 0;
		onTickTraffic = 0;
		prevOnTickTraffic = 0;
		resourceManagersNominalCPU = new HashMap<String, Integer>();
		resourceManagersNominalMemory = new HashMap<String, Integer>();
		resourceManagersCPUCost = new HashMap<String, Integer>();
		resourceManagersMemoryCost = new HashMap<String, Integer>();
		resourceManagersFreeCPU = new HashMap<String, Integer>();
		resourceManagersFreeMemory = new HashMap<String, Integer>();
		whereAreComponents = new HashMap<String, String>();
		componentsRequiredMemory = new HashMap<String, Integer>();
		componentsRequiredCPU = new HashMap<String, Integer>();
		networkMatrix = new NetworkMatrix();
		try {
			batteryWriter = new FileWriter(EctoMobileStarter.batteryFile);
			globalCostWriter = new FileWriter(EctoMobileStarter.globalCostFile);
			throughputWriter = new FileWriter(EctoMobileStarter.throughputFile);
			mobileCpuLoadWriter = new FileWriter(
					EctoMobileStarter.mobileCPULoadFile);
			mobileMemoryLoadWriter = new FileWriter(
					EctoMobileStarter.mobileMEMORYLoadFile);
			trafficWriter = new FileWriter(EctoMobileStarter.trafficFile);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void addComponentRequiredCPU(String componentName, int requiredCPU) {
		componentsRequiredCPU.put(componentName, new Integer(requiredCPU));
	}

	public void addComponentRequiredMemory(String componentName,
			int requiredMemory) {
		componentsRequiredMemory
				.put(componentName, new Integer(requiredMemory));
	}

	public void addCost(float addCost) {
		this.prevCost = this.currentCost;
		this.currentCost += addCost;
	}

	public void addFreeCpu(String resourceManagerName, int newFreeCpu) {
		int sum = resourceManagersFreeCPU.get(resourceManagerName).intValue();
		sum = sum + newFreeCpu;
		Logger.log("[STATUS] new free cpu per: " + resourceManagerName + " è: "
				+ sum);
		resourceManagersFreeCPU.put(resourceManagerName, new Integer(sum));
	}

	public void addFreeMemory(String resourceManagerName, int newFreeMemory) {
		int sum = resourceManagersFreeMemory.get(resourceManagerName)
				.intValue();
		sum = sum + newFreeMemory;
		Logger.log("[STATUS] new free memory per: " + resourceManagerName
				+ " è: " + sum);
		resourceManagersFreeMemory.put(resourceManagerName, new Integer(sum));
	}

	public void addNewLink(String nodeA, String nodeB, int QoS) {
		networkMatrix.addNewLink(nodeA, nodeB, QoS);
	}

	public void addNewResourceManager(String resourceManagerName,
			ResourceManager resourceManager) {
		resourceManagersNominalCPU.put(resourceManagerName,
				resourceManager.getNOMINAL_CPU());
		resourceManagersNominalMemory.put(resourceManagerName,
				resourceManager.getNOMINAL_MEMORY());
		resourceManagersCPUCost.put(resourceManagerName,
				resourceManager.getCostCPU());
		resourceManagersMemoryCost.put(resourceManagerName,
				resourceManager.getCostMemory());
		resourceManagersFreeCPU.put(resourceManagerName, new Integer(
				resourceManager.getNOMINAL_CPU()));
		resourceManagersFreeMemory.put(resourceManagerName, new Integer(
				resourceManager.getNOMINAL_MEMORY()));
		this.resourceManagersNumber++;
	}

	public ArrayList<String> getAvailableNodesName(int requiredCPU,
			int requiredMEM) {
		ArrayList<String> availableNodesName = new ArrayList<String>();
		Iterator<String> iterator = resourceManagersNominalCPU.keySet()
				.iterator();
		StringBuffer sb = new StringBuffer("============INIZIO RANKING NODI AVVIABILI==========\n\n");
		sb.append("COMPONENT :\n\trequiredCPU: "+requiredCPU+"\n\trequiredMEM: "+requiredMEM+"\n\n");
		while (iterator.hasNext()) {
			String key = iterator.next();
//			int freeCPU = resourceManagersFreeCPU.get(key).intValue();
//			int freeMEM = resourceManagersFreeMemory.get(key).intValue();
//			float fCPU = freeCPU/statusRequestCounter;
//			float fMEM = freeMEM/statusRequestCounter;
//			if (key.equals(EctoMobileStarter.MOBILE_NODE_NAME)) {
//				if (NetworkMatrix.mobileReachable) {
//					if (fCPU >= (float)requiredCPU
//							&& fMEM >= (float)requiredMEM)
//						availableNodesName.add(key);
//				}
//			} else {
//				if (fCPU >= requiredCPU
//						&& fMEM >= requiredMEM)
//					availableNodesName.add(key);
//			}			
			int nominalCPU = resourceManagersNominalCPU.get(key);
			int nominalMEM = resourceManagersNominalMemory.get(key);
			float averageFreeCPU = getAverageFreeCpu(key);
			float averageFreeMEM = getAverageFreeMemory(key);
			if(requiredCPU <= nominalCPU && requiredMEM <= nominalMEM){
				if(requiredCPU <= averageFreeCPU && requiredMEM <= averageFreeMEM){
					availableNodesName.add(key);
					sb.append("NODE "+key+":\n\tnominalCPU: "+nominalCPU+
							"\n\tnominalMEM: "+nominalMEM+
							"\n\taverageFreeCPU: "+averageFreeCPU+
							"\n\taverageFreeMEM: "+averageFreeMEM+
							"\n\tSCORE: NON CALCOLATO\n\n");
				}else{
					float score = calculateNodeScore(nominalCPU, nominalMEM, averageFreeCPU, averageFreeMEM, requiredCPU, requiredMEM);
					sb.append("NODE "+key+":\n\tnominalCPU: "+nominalCPU+
							"\n\tnominalMEM: "+nominalMEM+
							"\n\taverageFreeCPU: "+averageFreeCPU+
							"\n\taverageFreeMEM: "+averageFreeMEM+
							"\n\tSCORE: "+score+"\n\n");
					if(score > EctoMobileStarter.AVAILABLE_NODE_SCORE_TH){
						availableNodesName.add(key);						
					}
				}
			}
		}
		sb.append("=============FINE RANKING NODI AVVIABILI==========\n\n");
		Logger.logPolicy(sb.toString());
		return availableNodesName;
	}
	
	private float calculateNodeScore(int nominalCPU,int nominalMEM, 
			float averageFreeCPU, float averageFreeMEM, 
			int requiredCPU, int requiredMEM ){
		float nominalCPUPatialScore = (1 - ((float)requiredCPU/(float)nominalCPU))*EctoMobileStarter.MAX_RANK_VAL;
		float nominalMEMPatialScore = (1 - ((float)requiredMEM/(float)nominalMEM))*EctoMobileStarter.MAX_RANK_VAL;
		//averageCPUPartialScore e averageCPUPartialScore possono essere negativi negativi		
		float averageFreeCPUPatialScore = (1 - ((float)requiredCPU/(float)averageFreeCPU))*EctoMobileStarter.MAX_RANK_VAL;
		float averageFreeMEMPatialScore = (1 - ((float)requiredMEM/(float)averageFreeMEM))*EctoMobileStarter.MAX_RANK_VAL;
		float score = 0.5f*nominalCPUPatialScore+0.5f*nominalMEMPatialScore+0.20f*averageFreeCPUPatialScore+0.20f*averageFreeMEMPatialScore;
		return score;
	}

	public float getAverageFreeCpu(String resourseManagerName) {
		float averageResourceManagerFreeCpu = resourceManagersFreeCPU.get(resourseManagerName).floatValue();
		return averageResourceManagerFreeCpu / statusRequestCounter;
	}

	public float getAverageFreeMemory(String resourseManagerName) {
		float averageResourceManagerFreeCpu = resourceManagersFreeMemory.get(resourseManagerName).floatValue();
		return averageResourceManagerFreeCpu / statusRequestCounter;
	}

	public float getBatteryLevel() {
		return batteryLevel;
	}

	public HashMap<String, Integer> getComponentsRequiredCPU() {
		return componentsRequiredCPU;
	}

	public HashMap<String, Integer> getComponentsRequiredMemory() {
		return componentsRequiredMemory;
	}

	public Integer getLinkQoS(String nodeA, String nodeB) {
		return networkMatrix.getLinkQoS(nodeA, nodeB);
	}

	public long getMigrationCounter() {
		return migrationCounter;
	}

	public NetworkMatrix getNetworkMatrix() {
		return networkMatrix;
	}

	public long getPoltergeistIn() {
		return poltergeistIn;
	}

	public long getPoltergeistOut() {
		return poltergeistOut;
	}

	public HashMap<String, Integer> getResourceManagersCPUCost() {
		return resourceManagersCPUCost;
	}

	public HashMap<String, Integer> getResourceManagersFreeCPU() {
		return resourceManagersFreeCPU;
	}

	public HashMap<String, Integer> getResourceManagersFreeMemory() {
		return resourceManagersFreeMemory;
	}

	public HashMap<String, Integer> getResourceManagersNominalCPU() {
		return resourceManagersNominalCPU;
	}

	public HashMap<String, Integer> getResourceManagersNominalMemory() {
		return resourceManagersNominalMemory;
	}

	public int getResourceManagersNumber() {
		return resourceManagersNumber;
	}

	public HashMap<String, Integer> getResourseManagersMemoryCost() {
		return resourceManagersMemoryCost;
	}

	public int getStatusRequestCounter() {
		return statusRequestCounter;
	}

	public HashMap<String, String> getWhereAreComponents() {
		return whereAreComponents;
	}

	public void increaseIncomingMessagesToPoltergeist() {
		poltergeistIn++;
		currentTraffic++;
	}

	public void increaseMigrationCounter(String comp, String whereToExecute) {
		//migrationCounter++;
		String currentLocation = this.whereIsComponent(comp);
		int QoS = this.getNetworkMatrix().getLinkQoS(currentLocation, whereToExecute).intValue();
		float size = Component.size(this.componentsRequiredCPU.get(comp).intValue(), this.componentsRequiredMemory.get(comp).intValue());
		int generatedTraffic = Math.round(size / (1 + ((QoS-1)/11)));
		migrationCounter = migrationCounter + generatedTraffic;
		currentTraffic += generatedTraffic;
	}

	public void increaseOutcomingMessagesToPoltergeist() {
		poltergeistOut++;
		currentTraffic++;
	}

	public void incrementStatusRequestCounter() {
		this.statusRequestCounter++;
	}

	public void moveComponent(String component, String container) {
		whereAreComponents.put(component, container);
	}

	public void roam(String mobileNode) {
		networkMatrix.roam(mobileNode);
	}

	public void setBatteryLevel(float newLevel) {
		this.batteryLevel = newLevel;
	}

	public void setComponentsRequiredCPU(
			HashMap<String, Integer> componentsRequiredCPU) {
		this.componentsRequiredCPU = componentsRequiredCPU;
	}

	public void setComponentsRequiredMemory(
			HashMap<String, Integer> componentsRequiredMemory) {
		this.componentsRequiredMemory = componentsRequiredMemory;
	}

	public void setResourceManagersCPUCost(
			HashMap<String, Integer> resourceManagersCPUCost) {
		this.resourceManagersCPUCost = resourceManagersCPUCost;
	}

	public void setResourceManagersFreeCPU(
			HashMap<String, Integer> resourceManagersFreeCPU) {
		this.resourceManagersFreeCPU = resourceManagersFreeCPU;
	}

	public void setResourceManagersFreeMemory(
			HashMap<String, Integer> resourceManagersFreeMemory) {
		this.resourceManagersFreeMemory = resourceManagersFreeMemory;
	}

	public void setResourceManagersNominalCPU(
			HashMap<String, Integer> resourceManagersNominalCPU) {
		this.resourceManagersNominalCPU = resourceManagersNominalCPU;
	}

	public void setResourceManagersNominalMemory(
			HashMap<String, Integer> resourceManagersNominalMemory) {
		this.resourceManagersNominalMemory = resourceManagersNominalMemory;
	}

	public void setResourceManagersNumber(int resourceManagersNumber) {
		this.resourceManagersNumber = resourceManagersNumber;
	}

	public void setResourseManagersMemoryCost(
			HashMap<String, Integer> resourseManagersMemoryCost) {
		this.resourceManagersMemoryCost = resourseManagersMemoryCost;
	}

	public String whereIsComponent(String component) {
		return whereAreComponents.get(component);
	}

	private void writeBatteryLevel() throws IOException {
		String batteryS = Float.toString(batteryLevel);
		batteryWriter.append(batteryS.replace(".", ",") + "\n");
		batteryWriter.flush();
	}

	private void writeGlobalCost() throws IOException {
		globalCostWriter.append(currentCost + "\n");
		globalCostWriter.flush();
	}

	private void writeMobileCpuLoad() throws IOException {
		float mobileNominal = this.resourceManagersNominalCPU.get(
				EctoMobileStarter.MOBILE_NODE_NAME).floatValue();
		float mobileFree = (this.resourceManagersFreeCPU
				.get(EctoMobileStarter.MOBILE_NODE_NAME));
		float toWrite = mobileNominal
				- (mobileFree / this.statusRequestCounter);
		// mobileCpuLoadWriter.append(mobileNominal + " " + mobileFree + " " +
		// this.statusRequestCounter + "\n");
		mobileCpuLoadWriter.append(toWrite + "\n");
		mobileCpuLoadWriter.flush();
	}

	private void writeMobileMemoryLoad() throws IOException {
		float mobileNominal = this.resourceManagersNominalMemory.get(
				EctoMobileStarter.MOBILE_NODE_NAME).floatValue();
		float mobileFree = (this.resourceManagersFreeMemory
				.get(EctoMobileStarter.MOBILE_NODE_NAME));
		float toWrite = mobileNominal
				- (mobileFree / this.statusRequestCounter);
		// mobileMemoryLoadWriter.append(mobileNominal + " " + mobileFree + " "
		// + this.statusRequestCounter + "\n");
		mobileMemoryLoadWriter.append(toWrite + "\n");
		mobileMemoryLoadWriter.flush();
	}

	public void writeStats() {
		try {
			writeBatteryLevel();
			writeGlobalCost();
			writeTraffic();
			writeThroughput();
			writeMobileCpuLoad();
			writeMobileMemoryLoad();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void writeThroughput() throws IOException {
		throughputWriter.append(Float.toString(currentThroughput)+"\n");
		throughputWriter.flush();
	}

	private void writeTraffic() throws IOException {
		trafficWriter.append(Long
				.toString((poltergeistIn + poltergeistOut + migrationCounter))
				+ "\n");
		trafficWriter.flush();
	}
	
	public void addThroughtput(float throughput) {
		currentThroughput += throughput;
	}
	
	public void resetThroughput(){
		currentThroughput = 0;
	}
	
	public float getCurrentCost(){
		return currentCost;
	}
	
	public float getCurrentThroughput(){
		return currentThroughput;
	}

	public void finalizeStatistics() {
		try {
			this.batteryWriter.close();
			this.globalCostWriter.close();
			this.mobileCpuLoadWriter.close();
			this.mobileMemoryLoadWriter.close();
			this.trafficWriter.close();
			this.throughputWriter.close();
			System.out.println(EctoMobileStarter.TRAFFIC_P);
			System.out.println(EctoMobileStarter.COST_P);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}	

	public float getPrevCost() {
		return prevCost;
	}
	
	public long getCurrentTraffic(){
		return currentTraffic;
	}
	
	public void fixTrafficOnTick(){
		prevOnTickTraffic = onTickTraffic;
		onTickTraffic = currentTraffic;
	}
	
	public long getOnTickTraffic(){
		return onTickTraffic;
	}
	
	public long getPrevOnTickTraffic(){
		return prevOnTickTraffic;
	}
	
	public float getCostIncrementPerc(){
		return ((float)currentCost-(float)prevCost)/(float)prevCost;
	}
	
	public float getTrafficIncrementPerc(){
		return ((float)onTickTraffic-(float)prevOnTickTraffic)/(float)prevOnTickTraffic;
	}
	
}
